/**
 * \file test_vector2.cpp
 * 
 * \section COPYRIGHT
 *
 * Basis: A 3D Mathematics Library
 *
 * ---------------------------------------------------------------------
 *
 * Copyright (c) 2010, Don Olmstead
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *
 *  3. Neither the name of organization nor the names of its contributors may be
 *     used to endorse or promote products derived from this software without
 *     specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "basis_test.hpp"

/// The vector2 implementation to check
typedef basis::vector2<real_type, simd_type> vector2_type;

/**
 * Contains test values to check the vector2 interface.
 */
struct Vector2TestValues
{
	/// A real number type
	typedef vector2_type::value_type value_type;

	/**
	 * How close the values need to be to be considered equal.
	 *
	 * Used to compare floating point values.
	 */
	const value_type tolerance;
	/// An array containing a scalar.
	value_type scalar[4];
	/// An array containing some values.
	value_type values1[4];
	/// An array containing some values.
	value_type values2[4];

	/**
	 * Initializes the test values.
	 */
	Vector2TestValues()
	: tolerance((value_type)0.001)
	{
		// Initialize a scalar value
		scalar[0] = (value_type)2;
		scalar[1] = (value_type)2;
		scalar[2] = (value_type)2;
		scalar[3] = (value_type)2;

		// Initialize some vector arrays
		values1[0] = (value_type)1;
		values1[1] = (value_type)2;
		values1[2] = (value_type)0;
		values1[3] = (value_type)0;

		values2[0] = (value_type)9;
		values2[1] = (value_type)8;
		values2[2] = (value_type)0;
		values2[3] = (value_type)0;
	}

} ; // end struct Vector2TestValues

SUITE(Vector2Interface)
{

	//------------------------------------------------------------
	// Constructor tests
	//------------------------------------------------------------

	TEST_FIXTURE(Vector2TestValues, MultipleConstructor)
	{
		vector2_type test(values1[0], values1[1]);

		CHECK_VECTOR2_CLOSE(values1, test, tolerance);
	}

	TEST_FIXTURE(Vector2TestValues, ArrayConstructor)
	{
		vector2_type test(values1);

		CHECK_VECTOR2_CLOSE(values1, test, tolerance);
	}

	TEST_FIXTURE(Vector2TestValues, CopyConstructor)
	{
		vector2_type copy(values1);
		vector2_type test(copy);

		CHECK_VECTOR2_CLOSE(values1, copy, tolerance);
	}

	//------------------------------------------------------------
	// Comparison operators
	//------------------------------------------------------------

	TEST_FIXTURE(Vector2TestValues, EqualTest)
	{
		vector2_type lhs(values1);
		vector2_type rhs(values2);

		CHECK(lhs == lhs);
		CHECK(!(lhs == rhs));
	}

	TEST_FIXTURE(Vector2TestValues, NotEqualTest)
	{
		vector2_type lhs(values1);
		vector2_type rhs(values2);

		CHECK(lhs != rhs);
		CHECK(!(lhs != lhs));
	}

	//------------------------------------------------------------
	// Element access tests
	//------------------------------------------------------------

	TEST_FIXTURE(Vector2TestValues, ElementAssign)
	{
		vector2_type test(values1);

		test.x() = values2[0];
		test.y() = values2[1];

		CHECK_VECTOR2_CLOSE(values2, test, tolerance);
	}

	TEST(Vector4Size)
	{
		CHECK(vector2_type::size() == 2);
		CHECK(vector2_type::capacity() == 4);
	}

	//------------------------------------------------------------
	// Arithmetic operator tests
	//------------------------------------------------------------
	
	TEST_FIXTURE(Vector2TestValues, NegateOperator)
	{
		value_type expected[] =
		{
			-values1[0],
			-values1[1],
			-values1[2],
			-values1[3]
		} ;

		vector2_type test(values1);
		vector2_type negate = -test;

		CHECK_VECTOR2_CLOSE(expected, negate, tolerance);
	}

	TEST_FIXTURE(Vector2TestValues, AddOperator)
	{
		value_type expected[] =
		{
			values1[0] + values2[0],
			values1[1] + values2[1],
			values1[2] + values2[2],
			values1[3] + values2[3]
		} ;

		vector2_type lhs(values1);
		vector2_type rhs(values2);

		// Check + operator
		vector2_type add = lhs + rhs;

		CHECK_VECTOR2_CLOSE(expected, add, tolerance);
		
		// Check += operator
		lhs += rhs;

		CHECK_VECTOR2_CLOSE(expected, lhs, tolerance);
	}

	TEST_FIXTURE(Vector2TestValues, SubOperator)
	{
		value_type expected[] =
		{
			values1[0] - values2[0],
			values1[1] - values2[1],
			values1[2] - values2[2],
			values1[3] - values2[3]
		} ;

		vector2_type lhs(values1);
		vector2_type rhs(values2);

		// Check - operator
		vector2_type sub = lhs - rhs;

		CHECK_VECTOR2_CLOSE(expected, sub, tolerance);
		
		// Check -= operator
		lhs -= rhs;

		CHECK_VECTOR2_CLOSE(expected, lhs, tolerance);
	}

	TEST_FIXTURE(Vector2TestValues, MulOperator)
	{
		value_type expected[] =
		{
			values1[0] * values2[0],
			values1[1] * values2[1],
			values1[2] * values2[2],
			values1[3] * values2[3]
		} ;

		vector2_type lhs(values1);
		vector2_type rhs(values2);

		// Check * operator
		vector2_type mul = lhs * rhs;

		CHECK_VECTOR2_CLOSE(expected, mul, tolerance);
		
		// Check *= operator
		lhs *= rhs;

		CHECK_VECTOR2_CLOSE(expected, lhs, tolerance);
	}

	TEST_FIXTURE(Vector2TestValues, MulOperatorScalar)
	{
		value_type expected[] =
		{
			values1[0] * scalar[0],
			values1[1] * scalar[1],
			values1[2] * scalar[2],
			values1[3] * scalar[3]
		} ;

		vector2_type lhs(values1);
		vector2_type::scalar_type rhs(scalar[0]);

		// Check * operator
		vector2_type mul = lhs * rhs;

		CHECK_VECTOR2_CLOSE(expected, mul, tolerance);

		// Check *= operator
		lhs *= rhs;

		CHECK_VECTOR2_CLOSE(expected, lhs, tolerance);
	}

	TEST_FIXTURE(Vector2TestValues, DivOperator)
	{
		value_type expected[] =
		{
			values1[0] / values2[0],
			values1[1] / values2[1],
			values1[2] / values2[2],
			values1[3] / values2[3]
		} ;

		vector2_type lhs(values1);
		vector2_type rhs(values2);

		// Check / operator
		vector2_type div = lhs / rhs;

		CHECK_VECTOR2_CLOSE(expected, div, tolerance);
		
		// Check /= operator
		lhs /= rhs;

		CHECK_VECTOR2_CLOSE(expected, lhs, tolerance);
	}

	TEST_FIXTURE(Vector2TestValues, DivOperatorScalar)
	{
		value_type expected[] =
		{
			values1[0] / scalar[0],
			values1[1] / scalar[1],
			values1[2] / scalar[2],
			values1[3] / scalar[3]
		} ;

		vector2_type lhs(values1);
		vector2_type::scalar_type rhs(scalar[0]);

		// Check / operator
		vector2_type div = lhs / rhs;

		CHECK_VECTOR2_CLOSE(expected, div, tolerance);

		// Check /= operator
		lhs /= rhs;

		CHECK_VECTOR2_CLOSE(expected, lhs, tolerance);
	}

	//------------------------------------------------------------
	// Arithmetic tests
	//------------------------------------------------------------

	TEST_FIXTURE(Vector2TestValues, MinValues)
	{
		value_type expected[] =
		{
			(values1[0] < values2[0]) ? values1[0] : values2[0],
			(values1[1] < values2[1]) ? values1[1] : values2[1],
			(values1[2] < values2[2]) ? values1[2] : values2[2],
			(values1[3] < values2[3]) ? values1[3] : values2[3]
		} ;

		vector2_type lhs(values1);
		vector2_type rhs(values2);
		vector2_type min = vector2_type::min(lhs, rhs);

		CHECK_VECTOR2_CLOSE(expected, min, tolerance);
	}

	TEST_FIXTURE(Vector2TestValues, MaxValues)
	{
		value_type expected[] =
		{
			(values1[0] > values2[0]) ? values1[0] : values2[0],
			(values1[1] > values2[1]) ? values1[1] : values2[1],
			(values1[2] > values2[2]) ? values1[2] : values2[2],
			(values1[3] > values2[3]) ? values1[3] : values2[3]
		} ;

		vector2_type lhs(values1);
		vector2_type rhs(values2);
		vector2_type max = vector2_type::max(lhs, rhs);

		CHECK_VECTOR2_CLOSE(expected, max, tolerance);
	}

	//------------------------------------------------------------
	// Vector operation tests
	//------------------------------------------------------------

	TEST_FIXTURE(Vector2TestValues, DotProduct)
	{
		value_type expected_dot =
			values1[0] * values2[0] +
			values1[1] * values2[1] +
			values1[2] * values2[2] +
			values1[3] * values2[3];

		vector2_type lhs(values1);
		vector2_type rhs(values2);
		vector2_type::scalar_type dot = vector2_type::dot(lhs, rhs);

		CHECK_CLOSE(expected_dot, (value_type)dot, tolerance);
	}

	TEST_FIXTURE(Vector2TestValues, Length)
	{
		value_type expected_length_squared =
			values1[0] * values1[0] +
			values1[1] * values1[1] +
			values1[2] * values1[2] +
			values1[3] * values1[3];
		value_type expected_length = std::sqrt(expected_length_squared);

		vector2_type test(values1);
		vector2_type::scalar_type length_squared = test.length_squared();

		CHECK_CLOSE(expected_length_squared, (value_type)length_squared, tolerance);

		vector2_type::scalar_type length = test.length();

		CHECK_CLOSE(expected_length, (value_type)length, tolerance);
	}

	TEST_FIXTURE(Vector2TestValues, Distance)
	{
		value_type diff[] =
		{
			values1[0] - values2[0],
			values1[1] - values2[1],
			values1[2] - values2[2],
			values1[3] - values2[3]
		} ;

		value_type expected_distance_squared =
			diff[0] * diff[0] +
			diff[1] * diff[1] +
			diff[2] * diff[2] +
			diff[3] * diff[3];
		value_type expected_distance = std::sqrt(expected_distance_squared);

		vector2_type lhs(values1);
		vector2_type rhs(values2);
		vector2_type::scalar_type distance_squared = vector2_type::distance_squared(lhs, rhs);

		CHECK_CLOSE(expected_distance_squared, (value_type)distance_squared, tolerance);

		vector2_type::scalar_type distance = vector2_type::distance(lhs, rhs);

		CHECK_CLOSE(expected_distance, (value_type)distance, tolerance);
	}
	
	TEST_FIXTURE(Vector2TestValues, Normalize)
	{
		value_type expected_length_squared =
			values1[0] * values1[0] +
			values1[1] * values1[1] +
			values1[2] * values1[2] +
			values1[3] * values1[3];
		value_type inv_expected_length = (value_type)1 / std::sqrt(expected_length_squared);

		value_type expected[] =
		{
			values1[0] * inv_expected_length,
			values1[1] * inv_expected_length,
			values1[2] * inv_expected_length,
			values1[3] * inv_expected_length
		} ;

		vector2_type test(values1);
		vector2_type normal = vector2_type::normalize(test);

		CHECK_VECTOR2_CLOSE(expected, normal, tolerance);

		test.normalize();

		CHECK_VECTOR2_CLOSE(expected, test, tolerance);
	}

	//------------------------------------------------------------
	// Shuffle tests
	//------------------------------------------------------------

	TEST_FIXTURE(Vector2TestValues, ShuffleMethod)
	{
		// Create the simd_type
		vector2_type test(values1);

		// Create the shuffles
		vector2_type shuffle_xx = test.shuffle<0, 0>();
		vector2_type shuffle_xy = test.shuffle<0, 1>();
		vector2_type shuffle_yx = test.shuffle<1, 0>();
		vector2_type shuffle_yy = test.shuffle<1, 1>();

		// Create the arrays
		value_type expected_xx[] = { values1[0], values1[0] };
		value_type expected_xy[] = { values1[0], values1[1] };
		value_type expected_yx[] = { values1[1], values1[0] };
		value_type expected_yy[] = { values1[1], values1[1] };

		CHECK_VECTOR2_CLOSE(expected_xx, shuffle_xx, tolerance);
		CHECK_VECTOR2_CLOSE(expected_xy, shuffle_xy, tolerance);
		CHECK_VECTOR2_CLOSE(expected_yx, shuffle_yx, tolerance);
		CHECK_VECTOR2_CLOSE(expected_yy, shuffle_yy, tolerance);
	}

} // end SUITE(Vector2Interface)
